package org.matchergen.apt;

import java.io.IOException;
import java.util.Collections;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;

import org.matchergen.GenerateMatcher;

import com.sun.codemodel.CodeWriter;
import com.sun.mirror.type.VoidType;

public class Generator extends AbstractProcessor {
	@Override
	public Set<String> getSupportedAnnotationTypes() {
		return Collections.singleton("org.matchergen.GenerateMatcher");
	}

	@Override
	public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
		System.out.println("generating matchers");

		Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(GenerateMatcher.class);

		for (Element element : elements) {
			if (element.getKind().isClass()) {
				handleClass((TypeElement) element);
			} else
				throw new IllegalArgumentException();
		}

		return false;
	}

	protected void handleClass(TypeElement typeElement) {
		MatcherGenerator matcherGenerator = new MatcherGenerator(typeElement.getQualifiedName().toString());

		for (Element childElement : processingEnv.getElementUtils().getAllMembers(typeElement)) {
			// muss public sein
			if (childElement.getModifiers().contains(Modifier.PUBLIC)) {
				switch (childElement.getKind()) {
				case FIELD:
					handleField(matcherGenerator, (VariableElement) childElement);
					break;
				case METHOD:
					handleMethod(matcherGenerator, (ExecutableElement) childElement);
					break;
				}
			}
		}

		try {
			// matcherGenerator.write(outputDirectory);
			CodeWriter codeWriter = new AptCodeWriter(processingEnv.getFiler());

			matcherGenerator.write(codeWriter);
		} catch (IOException e) {
			System.out.println("Fehler");
			e.printStackTrace();
		}
	}

	protected void handleField(MatcherGenerator matcherGenerator, VariableElement fieldElement) {
		// System.out.printf(" - %s\n", fieldElement.getSimpleName());
		String typeClass = getTypeClass(fieldElement.asType().toString());

		matcherGenerator.generateMatcherMethod(fieldElement.getSimpleName().toString(), typeClass);
	}

	protected void handleMethod(MatcherGenerator matcherGenerator, ExecutableElement methodElement) {
		if (isPropertyMethod(methodElement)) {
			String name = methodElement.getSimpleName().toString();

			TypeMirror type = methodElement.getReturnType();

			if (!(type instanceof VoidType)) {
				String propName = methodName2PropertyName(name);

				matcherGenerator.generateMatcherMethod(propName, getTypeClass(type.toString()));
			}
		}
	}

	protected String getTypeClass(String name) {

		if (name.equals("int"))
			return "java.lang.Integer";
		else
			return name;
	}

	protected boolean isPropertyMethod(ExecutableElement methodElement) {
		String methodName = methodElement.getSimpleName().toString();

		boolean ignoredMethod = !methodName.equals("getClass") && !methodName.equals("hasCode");
		boolean hasPropertyName = (methodName.startsWith("get") || methodName.startsWith("is") || methodName.startsWith("has"))
				&& Character.isUpperCase(methodName.charAt(3));
		boolean isNotVoid = methodElement.getReturnType().getKind() != TypeKind.VOID;
		boolean hasNoParameters = methodElement.getParameters().size() == 0;

		return ignoredMethod && hasPropertyName && isNotVoid && hasNoParameters;
	}

	protected String methodName2PropertyName(String name) {
		String propName;

		if (name.startsWith("get") || name.startsWith("has")) {
			propName = Character.toLowerCase(name.charAt(3)) + name.substring(4);
		} else if (name.startsWith("is")) {
			propName = Character.toLowerCase(name.charAt(2)) + name.substring(3);
		} else
			throw new IllegalArgumentException();

		return propName;
	}
}
